Crate expect_test

source ·
Expand description

Minimalistic snapshot testing for Rust.

§Introduction

expect_test is a small addition over plain assert_eq! testing approach, which allows to automatically update tests results.

The core of the library is the expect! macro. It can be though of as a super-charged string literal, which can update itself.

Let’s see an example:

use expect_test::expect;

let actual = 2 + 2;
let expected = expect!["5"]; // or expect![["5"]]
expected.assert_eq(&actual.to_string())

Running this code will produce a test failure, as "5" is indeed not equal to "4". Running the test with UPDATE_EXPECT=1 env variable however would “magically” update the code to:

let actual = 2 + 2;
let expected = expect!["4"];
expected.assert_eq(&actual.to_string())

This becomes very useful when you have a lot of tests with verbose and potentially changing expected output.

Under the hood, the expect! macro uses file!, line! and column! to record source position at compile time. At runtime, this position is used to patch the file in-place, if UPDATE_EXPECT is set.

§Guide

expect! returns an instance of Expect struct, which holds position information and a string literal. Use Expect::assert_eq for string comparison. Use Expect::assert_debug_eq for verbose debug comparison. Note that leading indentation is automatically removed.

use expect_test::expect;

#[derive(Debug)]
struct Foo {
    value: i32,
}

let actual = Foo { value: 92 };
let expected = expect![["
    Foo {
        value: 92,
    }
"]];
expected.assert_debug_eq(&actual);

Be careful with assert_debug_eq - in general, stability of the debug representation is not guaranteed. However, even if it changes, you can quickly update all the tests by running the test suite with UPDATE_EXPECT environmental variable set.

If the expected data is too verbose to include inline, you can store it in an external file using the expect_file! macro:

use expect_test::expect_file;

let actual = 42;
let expected = expect_file!["./the-answer.txt"];
expected.assert_eq(&actual.to_string());

File path is relative to the current file.

§Suggested Workflows

I like to use data-driven tests with expect_test. I usually define a single driver function check and then call it from individual tests:

use expect_test::{expect, Expect};

fn check(actual: i32, expect: Expect) {
    let actual = actual.to_string();
    expect.assert_eq(&actual);
}

#[test]
fn test_addition() {
    check(90 + 2, expect![["92"]]);
}

#[test]
fn test_multiplication() {
    check(46 * 2, expect![["92"]]);
}

Each test’s body is a single call to check. All the variation in tests comes from the input data.

When writing a new test, I usually copy-paste an old one, leave the expect blank and use UPDATE_EXPECT to fill the value for me:

#[test]
fn test_division() {
    check(92 / 2, expect![[]])
}

See https://blog.janestreet.com/using-ascii-waveforms-to-test-hardware-designs/ for a cool example of snapshot testing in the wild!

§Alternatives

  • insta - a more feature full snapshot testing library.
  • k9 - a testing library which includes support for snapshot testing among other things.

§Maintenance status

The main customer of this library is rust-analyzer. The library is stable, it is planned to not release any major versions past 1.0.

§Minimal Supported Rust Version

This crate’s minimum supported rustc version is 1.60.0. MSRV is updated conservatively, supporting roughly 10 minor versions of rustc. MSRV bump is not considered semver breaking, but will require at least minor version bump.

Macros§

  • Creates an instance of Expect from string literal:
  • Creates an instance of ExpectFile from relative or absolute path:

Structs§

  • Self-updating string literal.
  • Self-updating file.
  • Position of original expect! in the source file.